// ------------------------------------------------------------------------------------------
// Licensed by Interprise Solutions.
// http://www.InterpriseSolutions.com
// For details on this license please visit  the product homepage at the URL above.
// THE ABOVE NOTICE MUST REMAIN INTACT.
// ------------------------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace InterpriseSuiteEcommerceCommon.InterpriseIntegration.JSONLib
{
    public enum SerializeOption
    {
        None = 0,
        Fields = 1,
        Properties = 2,
        WithSerializedAttributeOnly = 4,
        All = Fields | Properties 
    }

    internal interface IJSONMemberInfo
    {
        string Name { get;}
        object Value { get;}
    }

    internal class JSONMemberInfo
    {
        private string _name = string.Empty;
        private object _value = null;

        internal JSONMemberInfo(string name, object value)
        {
            _name = name;
            _value = value;
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }
        public object Value
        {
            get { return _value; }
            set { _value = value; }
        }
    }

    public class JSONSerializableAttribute : Attribute
    {
        private string _name = string.Empty;

        public JSONSerializableAttribute(string name)
        {
            _name = name;
        }

        public string Name
        {
            get { return _name; }
        }
    }

    public class JSONSerializer
    {
        private SerializeOption _option = SerializeOption.All;
        public JSONSerializer(SerializeOption option)
        {
            _option = option;
        }

        public string SerializeArray(IEnumerable enumerable)
        {
            StringBuilder output = new StringBuilder();
            JSONWriter writer = new JSONWriter(output);
            writer.HandleUnsupportedObject += new EventHandler<JSONUnsupportedObjectEventArgs>(JSONWriter_HandleUnsupportedObject);

            writer.WriteValue(enumerable);

            return output.ToString();
        }

        public string SerializeHash(IDictionary hash)
        {
            StringBuilder output = new StringBuilder();
            JSONWriter writer = new JSONWriter(output);
            writer.HandleUnsupportedObject += new EventHandler<JSONUnsupportedObjectEventArgs>(JSONWriter_HandleUnsupportedObject);

            writer.WriteValue(hash);

            return output.ToString();
        }

        public string Serialize(object context)
        {
            StringBuilder output = new StringBuilder();
            JSONWriter writer = new JSONWriter(output);
            writer.HandleUnsupportedObject += new EventHandler<JSONUnsupportedObjectEventArgs>(JSONWriter_HandleUnsupportedObject);

            SerializeCore(context, writer);

            return output.ToString();
        }

        private void JSONWriter_HandleUnsupportedObject(object sender, JSONSerializer.JSONUnsupportedObjectEventArgs e)
        {
            SerializeCore(e.Context, sender as JSONWriter);
        }

        private void SerializeCore(object context, JSONWriter writer)
        {
            writer.BeginWriteJson();

            IEnumerator<JSONMemberInfo> enumerator = DiscoverMembers(context);

            bool navigate = enumerator.MoveNext();

            while (navigate)
            {
                JSONMemberInfo member = enumerator.Current;
                writer.WriteName(member.Name);
                writer.WriteValue(member.Value);

                navigate = enumerator.MoveNext();
                if (navigate)
                {
                    writer.WriteSeparator();
                }
            }
            writer.EndWriteJson();
        }

        private IEnumerator<JSONMemberInfo> DiscoverMembers(object context)
        {
            List<JSONMemberInfo> members = new List<JSONMemberInfo>();
            Type contextType = context.GetType();

            bool serializeOnlyAttributedFields = ((_option & SerializeOption.WithSerializedAttributeOnly) == SerializeOption.WithSerializedAttributeOnly);

            if ((_option & SerializeOption.Fields) == SerializeOption.Fields)
            {
                FieldInfo[] fields = contextType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.DeclaredOnly);
                if (null != fields)
                {
                    foreach (FieldInfo field in fields)
                    {
                        JSONMemberInfo nfo = new JSONMemberInfo(field.Name, field.GetValue(context));
                        bool canJoin = true;

                        if (serializeOnlyAttributedFields)
                        {
                            JSONSerializableAttribute[] attributes = field.GetCustomAttributes(typeof(JSONSerializableAttribute), false) as JSONSerializableAttribute[];
                            if (attributes.Length == 1)
                            {
                                JSONSerializableAttribute attribute = attributes[0];

                                if (!CommonLogic.IsStringNullOrEmpty(attribute.Name))
                                {
                                    nfo.Name = attribute.Name;
                                }

                                canJoin = true;
                            }
                            else
                            {
                                canJoin = false;
                            }
                        }

                        if (canJoin)
                        {
                            members.Add(nfo);
                        }
                    }
                }
            }

            if ((_option & SerializeOption.Properties) == SerializeOption.Properties)
            {
                PropertyInfo[] properties = contextType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.DeclaredOnly);
                if (null != properties)
                {
                    foreach (PropertyInfo property in properties)
                    {
                        JSONMemberInfo nfo = new JSONMemberInfo(property.Name, property.GetValue(context, new object[] { }));

                        bool canJoin = true;

                        if (serializeOnlyAttributedFields)
                        {
                            JSONSerializableAttribute[] attributes = property.GetCustomAttributes(typeof(JSONSerializableAttribute), false) as JSONSerializableAttribute[];
                            if (attributes.Length == 1)
                            {
                                JSONSerializableAttribute attribute = attributes[0];

                                if (!CommonLogic.IsStringNullOrEmpty(attribute.Name))
                                {
                                    nfo.Name = attribute.Name;
                                }

                                canJoin = true;
                            }
                            else
                            {
                                canJoin = false;
                            }
                        }

                        if (canJoin)
                        {
                            members.Add(nfo);
                        }
                    }
                }
            }

            return members.GetEnumerator();
        }

        private class JSONUnsupportedObjectEventArgs : EventArgs
        {
            private object _context;

            internal JSONUnsupportedObjectEventArgs(object context)
            {
                _context = context;
            }

            public object Context
            {
                get { return _context; }
            }
        }

        private class JSONWriter
        {
            private StringBuilder _output = null;

            internal event EventHandler<JSONUnsupportedObjectEventArgs> HandleUnsupportedObject;

            internal JSONWriter(StringBuilder output)
            {
                _output = output;
            }

            public void BeginWriteJson()
            {
                _output.Append("{ ");
            }

            public void EndWriteJson()
            {
                _output.Append(" }");
            }

            public void WriteName(string name)
            {
                _output.AppendFormat("{0} : ", name);
            }

            public void WriteSeparator()
            {
                _output.Append(", ");
            }

            public void WriteValue(object value)
            {
                if (null == value)
                {
                    WriteNull();
                }
                else if (value is string)
                {
                    WriteString(value);
                }
                else if (value is decimal)
                {
                    WriteDecimal((decimal)value);
                }
                else if (value is int || value is float || value is double || value is short || value is long)
                {
                    WriteNumber(value);
                }
                else if (value is bool)
                {
                    WriteBoolean(value);
                }
                else if (value is IDictionary)
                {
                    WriteHash(value as IDictionary);
                }
                else if (value is IEnumerable)
                {
                    WriteArray(value as IEnumerable);
                }
                else if (value is Guid)
                {
                    WriteString(value.ToString());
                }
                else if (value.GetType().IsEnum)
                {
                    WriteString(value.ToString());
                }
                else
                {
                    if (null != value)
                    {
                        OnHandleUnsupportedObject(value);
                    }
                }
            }

            private void OnHandleUnsupportedObject(object context)
            {
                if (null != HandleUnsupportedObject)
                {
                    HandleUnsupportedObject(this, new JSONUnsupportedObjectEventArgs(context));
                }
            }

            private void WriteNull()
            {
                _output.Append("null");
            }

            private void WriteString(object value)
            {
                _output.AppendFormat("\"{0}\"", Security.JavascriptEscape((string)value));
            }

            private void WriteDecimal(decimal value)
            {
                _output.Append(value.ToString("0.00", new System.Globalization.CultureInfo("en-US").NumberFormat));
            }

            private void WriteNumber(object value)
            {
                _output.Append(value);
            }

            private void WriteBoolean(object value)
            {
                _output.Append(value.ToString().ToLower());
            }

            private void WriteArray(IEnumerable enumerable) 
            {
                if (null != enumerable)
                {
                    _output.Append("[ ");

                    IEnumerator enumerator = enumerable.GetEnumerator();

                    bool navigate = enumerator.MoveNext();
                    while (navigate)
                    {
                        WriteValue(enumerator.Current);

                        navigate = enumerator.MoveNext();
                        if (navigate)
                        {
                            WriteSeparator();
                        }
                    }

                    _output.Append(" ]");
                }
            }

            private void WriteHash(IDictionary hash)
            {
                if (null != hash)
                {
                    BeginWriteJson();

                    IEnumerator enumerator = hash.Keys.GetEnumerator();

                    bool navigate = enumerator.MoveNext();
                    while (navigate)
                    {
                        object key = enumerator.Current;

                        WriteName(key.ToString());
                        WriteValue(hash[key]);

                        navigate = enumerator.MoveNext();
                        if (navigate)
                        {
                            WriteSeparator();
                        }
                    }
                    EndWriteJson();
                }
            }
        }
    }
}
